package com.myzl.coal.client;

import cn.hutool.core.date.DatePattern;
import com.myzl.coal.client.device.CallBackR2000;
import com.myzl.coal.domain.Device;
import com.myzl.coal.domain.DeviceProtocol;
import com.myzl.coal.domain.RealTimeData;
import com.myzl.coal.service.*;
import com.myzl.coal.utils.Modbus4jUtils;
import com.myzl.common.constant.CacheKeyConstants;
import com.myzl.common.enums.*;
import com.serotonin.modbus4j.ProcessImageListener;
import com.serotonin.modbus4j.base.ModbusUtils;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;


/**
 * @author 91941
 */
@Component
@Slf4j
@Data
public class StartNettyServer implements CommandLineRunner {
    @Lazy
    @Autowired
    private  CacheData cacheData;
    private final IDeviceService deviceService;
    private final IDeviceProtocolService deviceProtocolService;
    private final IRealTimeDataService realTimeDataService;
    private final CallBackR2000 callBackR2000;
    private final IScaleService scaleService;
    private final StringRedisTemplate redisTemplate;
    private final IStatisticsService statisticsService;
    @Value("${projectId}")
    private String projectId;

    @Override
    public void run(String... args) {
        cacheData.refresh();
        List<Device> devices = deviceService.selectDeviceList(new Device());
        List<Device> devicesUpdate = devices.stream().map(m -> {
            m.setDeviceStatus(DeviceStatusEnum.OFFLINE.getCode());
            return m;
        }).collect(Collectors.toList());
        deviceService.saveOrUpdateBatch(devicesUpdate);


        DeviceProtocol deviceProtocolParams = new DeviceProtocol();
        List<DeviceProtocol> deviceProtocolList = deviceProtocolService.selectDeviceProtocolList(deviceProtocolParams);
        List<DeviceProtocol> collect = deviceProtocolList.stream().filter(f -> f.getServerType() == 1).collect(Collectors.toList());

        collect.forEach(e -> {
            startByType(null, e.getPort(), e);
        });

        Device params = new Device();
//        params.setIp("10.10.10.111");
        List<Device> deviceList = deviceService.selectDeviceList(params);
        for (Device device : deviceList) {
            startDevice(device.getId());
            //设备状态初始化
            RealTimeData realTimeDataParams = new RealTimeData();
            realTimeDataParams.setDeviceId(device.getId());
            realTimeDataParams.setDictValue(PublicDictValueEnum.DEVICE_STATUS.getCode());
            List<RealTimeData> realTimeDataList = realTimeDataService.selectRealTimeDataList(realTimeDataParams);
            if (CollectionUtils.isEmpty(realTimeDataList)) {
                realTimeDataParams.setDataValue(StatusStringType.UNKNOWN.getCode());
                realTimeDataService.save(realTimeDataParams);
            }
        }
        Map<Integer, List<Device>> deviceDeviceMap = deviceList.stream().collect(Collectors.groupingBy(Device::getDeviceType));
        List<Device> doubleOrderDeviceList = deviceDeviceMap.get(DeviceTypeEnum.DOUBLE_ORDER.getCode());
        List<Integer> slaveIds = doubleOrderDeviceList.stream().filter(f -> f.getDeviceConfigJSONObject().getInteger("slaveId") != null).map(m -> m.getDeviceConfigJSONObject().getInteger("slaveId")).collect(Collectors.toList());
        Map<Integer, Device> deviceSlaveIdMap = doubleOrderDeviceList.stream().filter(f -> f.getDeviceConfigJSONObject().getInteger("slaveId") != null).collect(Collectors.toMap(e -> e.getDeviceConfigJSONObject().getInteger("slaveId"), (a) -> a));

        List<Modbus4jUtils.Slave> slaves = slaveIds.stream().map(slaveId -> {
            ProcessImageListener processImageListener = new ProcessImageListener() {
                @Override
                public void coilWrite(int offset, boolean oldValue, boolean newValue) {
                    log.info("coilWrite,offset:{},oldValue:{},newValue:{},", offset, oldValue, newValue);
                }

                @Override
                public void holdingRegisterWrite(int offset, short oldValue, short newValue) {
                    log.info("holdingRegisterWrite,offset:{},oldValue:{},newValue:{},", offset, oldValue, newValue);
                    if (!"yulin".equals(projectId)) {
                        if (Modbus4jUtils.REGISTER_START == offset) {
                            if (newValue == 1) {
                                Device device = deviceSlaveIdMap.get(slaveId);
                                scaleService.resetScale(device.getId());
                                Modbus4jUtils.writeHoldingRegister(slaveId, ModbusUtils.TCP_PORT, Modbus4jUtils.REGISTER_START, Modbus4jUtils.REGISTER_DEFAULT_SHORT_VALUE);
                            }
                        } else if (PlcScaleGuoJiaWanEnum.ADDRESS_40029.getCode() == offset) {
                            if (newValue == 1) {
                                Short startYear = Modbus4jUtils.getHoldingRegister(slaveId, ModbusUtils.TCP_PORT, PlcScaleGuoJiaWanEnum.ADDRESS_40030.getCode());
                                Short startMonth = Modbus4jUtils.getHoldingRegister(slaveId, ModbusUtils.TCP_PORT, PlcScaleGuoJiaWanEnum.ADDRESS_40031.getCode());
                                Short startDay = Modbus4jUtils.getHoldingRegister(slaveId, ModbusUtils.TCP_PORT, PlcScaleGuoJiaWanEnum.ADDRESS_40032.getCode());
                                Short startHour = Modbus4jUtils.getHoldingRegister(slaveId, ModbusUtils.TCP_PORT, PlcScaleGuoJiaWanEnum.ADDRESS_40033.getCode());
                                Short startMinute = Modbus4jUtils.getHoldingRegister(slaveId, ModbusUtils.TCP_PORT, PlcScaleGuoJiaWanEnum.ADDRESS_40034.getCode());
                                Short endYear = Modbus4jUtils.getHoldingRegister(slaveId, ModbusUtils.TCP_PORT, PlcScaleGuoJiaWanEnum.ADDRESS_40035.getCode());
                                Short endMonth = Modbus4jUtils.getHoldingRegister(slaveId, ModbusUtils.TCP_PORT, PlcScaleGuoJiaWanEnum.ADDRESS_40036.getCode());
                                Short endDay = Modbus4jUtils.getHoldingRegister(slaveId, ModbusUtils.TCP_PORT, PlcScaleGuoJiaWanEnum.ADDRESS_40037.getCode());
                                Short endHour = Modbus4jUtils.getHoldingRegister(slaveId, ModbusUtils.TCP_PORT, PlcScaleGuoJiaWanEnum.ADDRESS_40038.getCode());
                                Short endMinute = Modbus4jUtils.getHoldingRegister(slaveId, ModbusUtils.TCP_PORT, PlcScaleGuoJiaWanEnum.ADDRESS_40039.getCode());
                                Callable<List<Object>> callable = new Callable<List<Object>>() {
                                    /**
                                     * Computes a result, or throws an exception if unable to do so.
                                     *
                                     * @return computed result
                                     * @throws Exception if unable to compute a result
                                     */
                                    @Override
                                    public List<Object> call() throws Exception {
                                        try {
                                            LocalDateTime dateTimeStart = LocalDateTime.of(startYear, startMonth, startDay, startHour, startMinute, 0);
                                            String startTime = dateTimeStart.format(DateTimeFormatter.ofPattern(DatePattern.NORM_DATETIME_PATTERN));
                                            LocalDateTime dateTimeEnd = LocalDateTime.of(endYear, endMonth, endDay, endHour, endMinute, 0);
                                            String endTime = dateTimeEnd.format(DateTimeFormatter.ofPattern(DatePattern.NORM_DATETIME_PATTERN));
                                            Device device = deviceSlaveIdMap.get(slaveId);
                                            List<Object> values = statisticsService.scaleDashboard1(device.getId(), startTime, endTime);
                                            values.add(Modbus4jUtils.REGISTER_DEFAULT_SHORT_VALUE);
                                            Modbus4jUtils.writeHoldingRegisterByMore(slaveId, ModbusUtils.TCP_PORT, PlcScaleGuoJiaWanEnum.ADDRESS_40025.getCode(), values);
                                            return values;
                                        } catch (Exception e) {
                                            log.info("", e);
                                            Modbus4jUtils.writeHoldingRegister(slaveId, ModbusUtils.TCP_PORT, PlcScaleGuoJiaWanEnum.ADDRESS_40025.getCode(), Modbus4jUtils.REGISTER_SHORT_VALUE_2);
                                        }
                                        return null;
                                    }

                                };
                                ExecutorService executorService = Executors.newSingleThreadExecutor();
                                executorService.submit(callable);
                            }
                        }
                    } else {
                        if (Modbus4jUtils.REGISTER_START == offset) {
                            if (newValue == 1) {
                                Modbus4jUtils.writeHoldingRegister(slaveId, ModbusUtils.TCP_PORT, Modbus4jUtils.REGISTER_START, Modbus4jUtils.REGISTER_DEFAULT_SHORT_VALUE);
                                Modbus4jUtils.writeHoldingRegisterByType(slaveId, ModbusUtils.TCP_PORT, 40005, Modbus4jUtils.REGISTER_DEFAULT_DOUBLE_VALUE);
                                redisTemplate.opsForHash().put(CacheKeyConstants.KEY, CacheKeyConstants.CURRENT_CLASS, System.currentTimeMillis() + "");
                            } else if (newValue == 2) {
                                Modbus4jUtils.writeHoldingRegister(slaveId, ModbusUtils.TCP_PORT, Modbus4jUtils.REGISTER_START, Modbus4jUtils.REGISTER_DEFAULT_SHORT_VALUE);
                                Modbus4jUtils.writeHoldingRegisterByType(slaveId, ModbusUtils.TCP_PORT, 40009, Modbus4jUtils.REGISTER_DEFAULT_DOUBLE_VALUE);
                                redisTemplate.opsForHash().put(CacheKeyConstants.KEY, CacheKeyConstants.DAY, System.currentTimeMillis() + "");
                            } else if (newValue == 3) {
                                Modbus4jUtils.writeHoldingRegister(slaveId, ModbusUtils.TCP_PORT, Modbus4jUtils.REGISTER_START, Modbus4jUtils.REGISTER_DEFAULT_SHORT_VALUE);
                                Modbus4jUtils.writeHoldingRegisterByType(slaveId, ModbusUtils.TCP_PORT, 40013, Modbus4jUtils.REGISTER_DEFAULT_DOUBLE_VALUE);
                                redisTemplate.opsForHash().put(CacheKeyConstants.KEY, CacheKeyConstants.MONTH, System.currentTimeMillis() + "");
                            } else if (newValue == 4) {
                                Modbus4jUtils.writeHoldingRegister(slaveId, ModbusUtils.TCP_PORT, Modbus4jUtils.REGISTER_START, Modbus4jUtils.REGISTER_DEFAULT_SHORT_VALUE);
                                Modbus4jUtils.writeHoldingRegisterByType(slaveId, ModbusUtils.TCP_PORT, 40017, Modbus4jUtils.REGISTER_DEFAULT_DOUBLE_VALUE);
                                redisTemplate.opsForHash().put(CacheKeyConstants.KEY, CacheKeyConstants.YEAR, System.currentTimeMillis() + "");
                            }
                        } else if (40025 == offset) {
                            if (newValue == 1) {
                                Short startYear = Modbus4jUtils.getHoldingRegister(slaveId, ModbusUtils.TCP_PORT, 40026);
                                Short startMonth = Modbus4jUtils.getHoldingRegister(slaveId, ModbusUtils.TCP_PORT, 40027);
                                Short startDay = Modbus4jUtils.getHoldingRegister(slaveId, ModbusUtils.TCP_PORT, 40028);
                                Short startHour = Modbus4jUtils.getHoldingRegister(slaveId, ModbusUtils.TCP_PORT, 40029);
                                Short startMinute = Modbus4jUtils.getHoldingRegister(slaveId, ModbusUtils.TCP_PORT, 40030);
                                Short endYear = Modbus4jUtils.getHoldingRegister(slaveId, ModbusUtils.TCP_PORT, 40031);
                                Short endMonth = Modbus4jUtils.getHoldingRegister(slaveId, ModbusUtils.TCP_PORT, 40032);
                                Short endDay = Modbus4jUtils.getHoldingRegister(slaveId, ModbusUtils.TCP_PORT, 40033);
                                Short endHour = Modbus4jUtils.getHoldingRegister(slaveId, ModbusUtils.TCP_PORT, 40034);
                                Short endMinute = Modbus4jUtils.getHoldingRegister(slaveId, ModbusUtils.TCP_PORT, 40035);
                                Callable<List<Object>> callable = new Callable<List<Object>>() {
                                    /**
                                     * Computes a result, or throws an exception if unable to do so.
                                     *
                                     * @return computed result
                                     * @throws Exception if unable to compute a result
                                     */
                                    @Override
                                    public List<Object> call() throws Exception {
                                        try {
                                            LocalDateTime dateTimeStart = LocalDateTime.of(startYear, startMonth, startDay, startHour, startMinute, 0);
                                            String startTime = dateTimeStart.format(DateTimeFormatter.ofPattern(DatePattern.NORM_DATETIME_PATTERN));
                                            LocalDateTime dateTimeEnd = LocalDateTime.of(endYear, endMonth, endDay, endHour, endMinute, 0);
                                            String endTime = dateTimeEnd.format(DateTimeFormatter.ofPattern(DatePattern.NORM_DATETIME_PATTERN));
                                            Device device = deviceSlaveIdMap.get(slaveId);
                                            List<Object> values = statisticsService.scaleDashboard1(device.getId(), startTime, endTime);
                                            values.add(Modbus4jUtils.REGISTER_DEFAULT_SHORT_VALUE);
                                            Modbus4jUtils.writeHoldingRegisterByMore(slaveId, ModbusUtils.TCP_PORT, 40021, values);
                                            return values;
                                        } catch (Exception e) {
                                            log.info("", e);
                                            Modbus4jUtils.writeHoldingRegister(slaveId, ModbusUtils.TCP_PORT, 40025, Modbus4jUtils.REGISTER_SHORT_VALUE_2);
                                        }
                                        return null;
                                    }

                                };
                                ExecutorService executorService = Executors.newSingleThreadExecutor();
                                executorService.submit(callable);
                            }
                        }
                    }

                }
            };
            return new Modbus4jUtils.Slave(slaveId, processImageListener);
        }).collect(Collectors.toList());


        Modbus4jUtils.getTcpSlave(slaves, ModbusUtils.TCP_PORT);


    }

    public void startDevice(Integer deviceId) {
        Device device = cacheData.getDevice(deviceId);
        startDevice(device);
    }

    public void startDevice(Device device) {
        if (Objects.isNull(device) || ObjectUtils.isEmpty(device.getIp()) || ObjectUtils.isEmpty(device.getPort())) {
            log.info("device not exists ip or port deviceId:{}", device.getId());
            return;
        }

        DeviceProtocol deviceProtocol = cacheData.getDeviceProtocolMap(device.getDeviceType());
        if (deviceProtocol == null || ObjectUtils.isEmpty(device.getPort())) {
            log.info("deviceProtocol not exists deviceType:{}", device.getDeviceType());
            return;
        }
        if (Objects.equals(deviceProtocol.getServerType(), ServerTypeEnum.CLIENT.getCode())) {
            startByType(device.getIp(), device.getPort(), deviceProtocol);
        }
    }


    private void startByType(String ip, int port, DeviceProtocol deviceProtocol) {
        log.info("open collection!,ip:{},port:{}", ip, port);
        switch (CommunicationProtocolEnum.getValue(deviceProtocol.getCommunicationProtocol())) {
            case TCP:
            case WEBSOCKET:
                if (Objects.equals(deviceProtocol.getServerType(), ServerTypeEnum.CLIENT.getCode())) {
                    NettyBootStrap.tcpClient(ip, port, NettyBootStrap.getHandler(deviceProtocol.getDeviceProtocol()));
                } else {
                    NettyBootStrap.tcpServer(port, NettyBootStrap.getHandler(deviceProtocol.getDeviceProtocol()));
                }
                break;
            case UDP:
                NettyBootStrap.udp(port, NettyBootStrap.getHandler(deviceProtocol.getDeviceProtocol()));
                break;
            case HTTP:
            case RTSP:
                break;
            case RFID:
                callBackR2000.connect(ip, port);
                break;

            default:
                break;

        }
    }


    public void stopDevice(Integer deviceId) {
        Device device = cacheData.getDevice(deviceId);
        stopDevice(device);
    }

    public void stopDevice(Device device) {
        String key = BaseHandler.getKey(device.getIp(), device.getPort());
        DeviceProtocol deviceProtocol = deviceProtocolService.cacheByDeviceType(device.getDeviceType());

        if (Objects.equals(ServerTypeEnum.SERVER.getCode(), deviceProtocol.getServerType())) {
            NettyBootStrap.close(device.getPort());
        } else {
            BaseHandler.close(key);
        }

    }

}
